#####################################################################
# File:         UserAccessRules.py
# Product:      KnowledgeEngine
#
# Copyright:    Copyright (c) 2002, Faulkner Ltd.
# Author:       Tom Cameron
#
# Description:  This file defines the UserAccessRules class, a simple
#               container for defining the Model-Assessment relationships
#               for an individual user in an Environment
#
# See Also:     ElementContainer, Element
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.KnowledgeEngineCore import *
from Products.KnowledgeEngine.Element import Element, ElementInfo
from Products.KnowledgeEngine.PropertyDefinition import PropertyDefinition
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyTypes import *

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from ImageFile import ImageFile

#####################################################################
## Python Library Imports
# (none)

#####################################################################
## UserAccessRulesInfo Class Definition
class UserAccessRulesInfo(ElementInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """

    #---------------------------------------------------------------#

    def getClassName(self):
        """
            Returns the name of the class for which
            we are providing information.
        """
        return "UserAccessRules"

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """

        return "User Access Rules"

    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            Return the identity type.
        """

        return "uar"

    #---------------------------------------------------------------#

    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """

        return ImageFile('www/icon.gif', globals())

    #---------------------------------------------------------------#

    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in
            the specified container (containerInstance)

            NOTE: This is from this class' instance perspective.
        """

        #UserAccessRules are only permitted in UserAccessManagers
        return containerClassInfo.isInstanceOf('UserAccessManager')

    #---------------------------------------------------------------#

    def newInstance(self, id, identity, REQUEST=None):
        """
            Creates and returns an instance of the class for which
            this class is defining class information.

            This method may be overriden by subclasses to provide
            specialised object construction using a REQUEST.

            This method is specifically called by
            GenericObject.manage_ObjectConstruction to construct
            instances of requested classes.

            eg: Calling CheckboxInfo.newInstance(...) will create
            a new instance of the Checkbox class.
        """

        return UserAccessRules(identity, REQUEST['username'])

    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register
            class specific properties using the PropertyReflection interface.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()
        """

        #initialise the super-class properties for reflection
        ElementInfo.onConfigureProperties(self)

        #set all of the properties to be not usable for constructors!
        #this will ensure that a constructor will not be built for this
        #type of component
        for propertyDefinition in self.getPropertyDefinitions():
            propertyDefinition.setIsForConstructor(false)

    #---------------------------------------------------------------#

#####################################################################
## UserAccessRules Class Definition
class UserAccessRules(Element):
    """
        A class to represent the UserAccessRules for a User
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = UserAccessRulesInfo()

    #---------------------------------------------------------------#

    manageModels = DTMLFile('dtml/manageModels', globals())
    manageAssessments = DTMLFile('dtml/manageAssessments', globals())
    manageReports = DTMLFile('dtml/manageReports', globals())

    #---------------------------------------------------------------#

    def __init__(self, identity, username=""):
        "Constructor for the object"

        self.id = str(username)
        self.title = username
        self.username = username

        self.models = {}
        self.assessments = {}

        Element.__init__(self, self.id, identity)

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object
            has been loaded into memory.

            This is useful for performing backwards compatibility
        """

        #perform default onAfterInstanciation processing
        Element.onAfterInstanciation(self)

        #add the management tabs
        self.addManagementTab('Models', 'manageModels', None, false)
        self.addManagementTab('Assessments', 'manageAssessments', None, false)
        self.addManagementTab('Reports', 'manageReports', None, false)

    #---------------------------------------------------------------#

    def getUserName(self):
        """
            Returns the name of the user that owns these UserAccessRules
        """

        return self.username

    #---------------------------------------------------------------#

    def getModels(self):
        """
            Returns a list of the model objects that the user has been
            linked to. This is not so simple because we only store the
            model ids and we have the 'ALL' value which represents all
            other models.

            If the user has 'ALL' linked, then I just get all the models
            and if not I get the list of ids then convert them back to
            actual model objects before returning the list.
        """

        ret = []
        if self.models.has_key('ALL') == 1:
            ret = self.getEnvironment().getModels()
        else:
            for x in self.models.keys():
                ret = ret + [self.getEnvironment().getModel(x)]
        return ret

    #---------------------------------------------------------------#

    def getModelIdentities(self):
        """
            Returns a list of the Model Identities to which the user
            has been granted access (of some sort)
        """

        return [model.getIdentity() for model in self.getModels()]

    #---------------------------------------------------------------#

    def getAssessmentName(self, modelIdentity, aid):
        """
        Returns Name of a particular assessment
        """

        return self.assessments[modelIdentity][aid]['name']

    #---------------------------------------------------------------#

    def buildModelDict(self, REQUEST):
        """
        Takes the REQUEST, looks up form values and then builds
        a dictionary to define user-model links.

        The dictionary is of the format
        models = {
                'modelidentity1':{'maxassessments':5, 'status':'open'},
                'modelidentity2':{'maxassessments':5, 'status':'open'},
                'modelidentity3':{'maxassessments':6, 'status':'lock'},
                'modelidentity4':{'maxassessments':5, 'status':'open'},
                'ALL':{'maxassessments':5, 'status':'lock'}
                }
        This way we can always add new properties later and you can
        store as many models as you wish.
        The 'ALL' object represents all models that have not been
        explicitly specified otherwise. It is optional.
        """

        ret = {}
        if REQUEST.has_key('links'):
            for m in REQUEST.links:
                s ={}
                if len(REQUEST.form[m]) == 1:
                    s.update({'maxassessments':REQUEST.form[m][0]})
                    s.update({'status':'open'})
                if len(REQUEST.form[m]) == 2:
                    s.update({'maxassessments':REQUEST.form[m][1]})
                    s.update({'status':str(REQUEST.form[m][0])})
                ret.update({m:s})
        return ret

    #---------------------------------------------------------------#

    def setMaxModelAssessments(self, modelIdentity, nrAssessments):
        """
            Sets the maximum number of assessments permitted for a model
        """

        #get the access for the specified model
        if self.isLinked(modelIdentity):
            lModelRule = self.models[modelIdentity]
        else:
            lModelRule = {'maxassessments':0, 'status':'open'}

        #set the number of assessments
        lModelRule['maxassessments'] = nrAssessments

        #are there any assessment (if it's <= zero we should unlink)
        if lModelRule['maxassessments'] <= 0:
            del self.models[modelIdentity]
        else:
            self.models[modelIdentity] = lModelRule

        #the property/object has now changed
        self._p_changed = 1

    #---------------------------------------------------------------#

    def addMaxModelAssessments(self, modelIdentity, nrAssessments):
        """
            Adds a specified number of assessments to the specified model
        """

        #get the access for the specified model
        if self.isLinked(modelIdentity):
            lModelRule = self.models[modelIdentity]
        else:
            lModelRule = {'maxassessments':0, 'status':'open'}

        #set the number of assessments
        lModelRule['maxassessments'] = int(lModelRule['maxassessments']) + nrAssessments

        #are there any assessment (if it's <= zero we should unlink)
        if lModelRule['maxassessments'] <= 0:
            if self.models.has_key(modelIdentity):
                del self.models[modelIdentity]
        else:
            self.models[modelIdentity] = lModelRule

        #the property/object has now changed
        self._p_changed = 1

    #---------------------------------------------------------------#

    def setModelAccess(self, REQUEST):
        """
            Builds the new model dictionary and stores it
        """

        self.models = self.buildModelDict(REQUEST)

        #the property/object has now changed
        self._p_changed = 1

    #---------------------------------------------------------------#

    def isLinked(self, modelIdentity):
        """
        returns true if the model is explicitly linked to this user.

        Note: see 'hasAccess' below if you want to check if the user has
        permissions.

        We have to use the 'try' command here because there may be
        no list at all.
        """

        try:
            return self.models.has_key(modelIdentity)
        except:
            return false

    #---------------------------------------------------------------#

    def hasAccess(self, modelIdentity):
        """
        returns true if the user has access to this model. This is
        subtally different to the isLinked which checks for explicit
        linking, but if the user has access to 'ALL', then this
        will also return true

        We have to use the 'try' command here because there may be
        no list at all.
        """

        #check if the user has access to ALL
        try:
            ret = self.models.has_key('ALL')
        except:
            ret = false
        if ret == true:
            return ret

        #otherwise check for explicit linking to model
        try:
            return self.models.has_key(modelIdentity)
        except:
            return false

    #---------------------------------------------------------------#

    def isLocked(self, modelIdentity):
        """
            returns true if the model is locked. Note it must also
            ckeck for the 'ALL' model.
        """

        if modelIdentity == 'ALL':
            try:
                return self.models['ALL']['status'] == 'lock'
            except:
                return false
        else:
            try:
                return self.models[modelIdentity]['status'] == 'lock'
            except:
                return false

    #---------------------------------------------------------------#

    def getMaxAssessments(self, modelIdentity):
        """
        returns the maxassessments id for the given model. Note it
        must also check for the 'ALL' model
        """

        #have we asked for the 'ALL' model?
        if modelIdentity == 'ALL':
            try:
                return self.models['ALL']['maxassessments']
            except:
                return 0

        #asked for a specific model, so let's use it
        else:
            try:
                return self.models[modelIdentity]['maxassessments']
            except:
                #if a specific model fails, fall back to the 'ALL' model
                try:
                    return self.models['ALL']['maxassessments']
                except:
                    return 0

    #---------------------------------------------------------------#

    def addAssessment(self, modelIdentity, name=None):
        """
            Adds a new assessment to the users list
        """

        aid = generateIdentity('aid')

        if name == None:
            name = 'New Assessment'
        if name == "":
            name = 'New Assessment'

        newassessment = {}
        newassessment['name'] = name
        newassessment['startdate'] = time()
        newassessment['status'] = 'open'

        tempassessments = self.assessments

        if tempassessments.has_key(modelIdentity) == 1:
            tempassessments[modelIdentity][aid] = newassessment
        else:
            tempassessments[modelIdentity] = {}
            tempassessments[modelIdentity][aid] = newassessment

        self.assessments = tempassessments

        #the property/object has now changed
        self._p_changed = 1

    #---------------------------------------------------------------#

    def getStatus(self, modelIdentity):
        """
        returns the status for the given model
        """

        if self.getUserModelModels().count(modelid) > 0:
            return self.models[modelIdentity]['status']
        else:
            return None

    #---------------------------------------------------------------#

    def getAssessments(self, modelIdentity=None):
        """
        returns a list of all the assessment dictionaries for a
        specified model, returns all if model not specified
        """

        if modelIdentity == None:
            ret = []
            for m in self.assessments.keys():
                ret = ret + self.assessments[m].items()
            return ret
        if self.assessments.has_key(modelIdentity) == 1:
            return self.assessments[modelIdentity].items()
        else:
            return []

    #---------------------------------------------------------------#

    def updateAssessments(self, modelIdentity, REQUEST):
        """
            Sets all the assessment values based on form values in the
            REQUEST namespace
        """

        #get all of the assessment ids for the model
        assessments = self.assessments[modelIdentity].keys()

        #lock and unlock assessments as specified by the request
        form = REQUEST.form
        for aid in assessments:
            if aid in form.keys():
                if 'lock' in form[aid]:
                    self.lockAssessment(modelIdentity, aid)
                else:
                    self.unlockAssessment(modelIdentity, aid)

        #delete assessments as specified by the request
        assessmentsToDelete = [aid for aid in assessments if aid in form.keys() and 'delete' in form[aid]]
        for aid in assessmentsToDelete:
            self.deleteAssessment(modelIdentity, aid)

        #the property/object has now changed
        self._p_changed = 1

    #---------------------------------------------------------------#

    def isAssessmentLocked(self, modelIdentity, aid):
        """
            Returns TRUE if the specified assessment is locked
        """

        if self.assessments[modelIdentity].has_key(aid):
            return self.assessments[modelIdentity][aid]['status'] == 'lock'
        else:
            return false

    #---------------------------------------------------------------#

    def deleteAssessment(self, modelIdentity=None, aid=None, model=None, assessment=None):
        """
            Deletes the specified assessment for the specified model.

            If a model is provided, modelIdentity is ignored
            If an assessment cache is provided, aid is ignored
        """

        #do we have a model?
        if not model is None:
            modelIdentity = model.getIdentity()

        #do we have an assessment?
        if not assessment is None:
            aid = assessment.getIdentity()

        #is the assessment unlocked? (only lock if it is unlocked)
        if not self.isAssessmentLocked(modelIdentity, aid):

            #get the model from the environment (if we have to)
            if model is None:
                model = self.getEnvironment().getModel(modelIdentity)

            #get the assessment from the model (if we have to)
            if assessment is None:
                assessment = model.getAssessment(aid)

            #handle the locking event for the model
            if model.isEventEnabled('onBeforeDeleteAssessment'):
                model.getEnabledEvent('onBeforeDeleteAssessment').execute(assessment)

            #delete the assessment (from the user)
            del self.assessments[modelIdentity][aid]

            #todo: delete the model assessment

            #the property/object has now changed
            self._p_changed = 1

    #---------------------------------------------------------------#

    def lockAssessment(self, modelIdentity=None, aid=None, model=None, assessment=None):
        """
            Locks the specified assessment for the specified model.

            If a model is provided, modelIdentity is ignored
            If an assessment cache is provided, aid is ignored
        """

        #do we have a model?
        if not model is None:
            modelIdentity = model.getIdentity()

        #do we have an assessment?
        if not assessment is None:
            aid = assessment.getIdentity()

        #is the assessment unlocked? (only lock if it is unlocked)
        if not self.isAssessmentLocked(modelIdentity, aid):

            #set the assessment status for the user to be locked
            self.assessments[modelIdentity][aid]['status'] = 'lock'

            #get the model from the environment (if we have to)
            if model is None:
                model = self.getEnvironment().getModel(modelIdentity)

            #get the assessment from the model (if we have to)
            if assessment is None:
                assessment = model.getAssessment(aid)

            #handle the locking event for the model
            if model.isEventEnabled('onBeforeLockAssessment'):
                model.getEnabledEvent('onBeforeLockAssessment').execute(assessment)

            #the property/object has now changed
            self._p_changed = 1

    #---------------------------------------------------------------#

    def unlockAssessment(self, modelIdentity=None, aid=None, model=None, assessment=None):
        """
            Unlocks the specified assessment for the specified model

            If a model is provided, modelIdentity is ignored
            If an assessment cache is provided, aid is ignored
        """

        #do we have a model?
        if not model is None:
            modelIdentity = model.getIdentity()

        #do we have an assessment?
        if not assessment is None:
            aid = assessment.getIdentity()

        #is the assessment locked? (only unlock if it is locked)
        if self.isAssessmentLocked(modelIdentity, aid):

            #set the assessment status for the user to be 'open'
            self.assessments[modelIdentity][aid]['status'] = 'open'

            #get the model from the environment (if we have to)
            if model is None:
                model = self.getEnvironment().getModel(modelIdentity)

            #get the assessment from the model (if we have to)
            if assessment is None:
                assessment = model.getAssessment(aid)

            #handle the unlocking event for the model
            if model.isEventEnabled('onBeforeUnlockAssessment'):
                model.getEnabledEvent('onBeforeUnlockAssessment').execute(assessment)

            #the property/object has now changed
            self._p_changed = 1

    #---------------------------------------------------------------#

    def isAtMaxAssessments(self, modelIdentity):
        """
        Returns TRUE if the maxassessments has been reached for the
        given model
        """

        #get the maximum assessments for this user
        maxassessments = self.getMaxAssessments(modelIdentity)

        if len(self.assessments) > 0:
            if self.assessments.has_key(modelIdentity):
                numassessments = len(self.assessments[modelIdentity].keys())
            else:
                numassessments = 0
        else:
            numassessments = 0
        return numassessments >= maxassessments

    #---------------------------------------------------------------#

    def resetModels(self):
        """
        for some reason the values get corrupt, this method just
        resets the values to an empty dictionary.
        """

        self.models = {}

        #the property/object has now changed
        self._p_changed = 1

    #---------------------------------------------------------------#

    def resetAssessments(self):
        """
        for some reason the values get corrupt, this method just
        resets the values to an empty dictionary.
        """

        self.assessments = {}

        #the property/object has now changed
        self._p_changed = 1

    #---------------------------------------------------------------#

    def getNumberOfLockedAssessments(self, modelIdentity):
        """
        Returns the number of locked assessments - used in /home view
        """

        number = 0
        if self.assessments.has_key(modelIdentity):  ###check for key
            for aid in self.assessments[modelIdentity].keys():
                if self.isAssessmentLocked(modelIdentity, aid):
                    number = number + 1
            return number
        else:
            return 0  
            
    #---------------------------------------------------------------#

    def getNumberOfOpenAssessments(self, modelIdentity):
        """
        Returns the number of open assessments - used in /home view
        """

        number = 0
        if self.assessments.has_key(modelIdentity):  ###check for key
            for aid in self.assessments[modelIdentity].keys():
                if not self.isAssessmentLocked(modelIdentity, aid):
                    number = number + 1
            return number
        else:
            return 0    
            
    #---------------------------------------------------------------#

#####################################################################
## Class Initialisation

# Register Class Information with the Knowledge Engine
registerGenericObjectClass(UserAccessRules)

#####################################################################
